package com.jianjun.framework.web.config;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.cron.CronUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.jfinal.aop.Aop;
import com.jfinal.config.Constants;
import com.jfinal.config.Handlers;
import com.jfinal.config.Interceptors;
import com.jfinal.config.JFinalConfig;
import com.jfinal.config.Plugins;
import com.jfinal.config.Routes;
import com.jfinal.core.ActionHandler;
import com.jfinal.json.Jackson;
import com.jfinal.json.JacksonFactory;
import com.jfinal.kit.Okv;
import com.jfinal.kit.TimeKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.DbKit;
import com.jfinal.plugin.activerecord.IDataSourceProvider;
import com.jfinal.plugin.activerecord.NullDataSource;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.plugin.ehcache.EhCachePlugin;
import com.jfinal.plugin.hikaricp.HikariCpPlugin;
import com.jfinal.plugin.redis.RedisPlugin;
import com.jfinal.render.ErrorRenderRmVersion;
import com.jfinal.template.Engine;
import com.jianjun.common.constant.CommonAttribute;
import com.jianjun.common.util.AllConfig;
import com.jianjun.common.util.SecurityUtils;
import com.jianjun.common.util.UtilJackson;
import com.jianjun.framework.web.handler.XssHandler;
import com.jianjun.framework.web.interceptor.CORSGlobalInterceptor;
import com.jianjun.framework.web.interceptor.DataScopeInterceptor;
import com.jianjun.framework.web.interceptor.JwtAuthenticationTokenInterceptor;
import com.jianjun.project.common.model._MappingKit;
import com.jianjun.project.monitor.job.SysJobService;
import com.jianjun.project.system.dict.SysDictTypeService;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.DiskStoreConfiguration;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author Source
 */
@Slf4j
public class WebInitial extends JFinalConfig {
    private static final ErrorRenderRmVersion errorRenderRmVersion = Aop.get(ErrorRenderRmVersion.class);
    private final SysDictTypeService sysDictTypeService = Aop.get(SysDictTypeService.class);
    private final SysJobService sysJobService = Aop.get(SysJobService.class);

    private final String redisHost = AllConfig.CONFIG.get("redis.host");
    private final Integer redisPort = AllConfig.CONFIG.getInt("redis.port");
    private final Integer redisTimeout = AllConfig.CONFIG.getInt("redis.timeout");
    private final String redisPassword = AllConfig.CONFIG.get("redis.password");
    private final Integer redisDatabase = AllConfig.CONFIG.getInt("redis.database");
    private final Boolean xssEnabled = AllConfig.CONFIG.getBoolean("xss.enabled");
    private final String xssExcludes = AllConfig.CONFIG.get("xss.excludes");
    private final Boolean redisEnabled = AllConfig.CONFIG.getBoolean("redis.enabled");

    /**
     * 配置常量值
     */
    @Override
    public void configConstant(Constants me) {
        // 开启对 jfinal web 项目组件 Controller、Interceptor、Validator 的注入
        me.setInjectDependency(true);
        // 配置对超类中的属性进行注入
        me.setInjectSuperClass(true);
        // 配置开发模式，true 值为开发模式
        me.setDevMode(true);
        // 配置 encoding，默认为 UTF8
        me.setEncoding(CommonAttribute.ENCODING);
        // 配置 json 转换 Date 类型时使用的 data parttern
        me.setJsonDatePattern(CommonAttribute.DATE_FORMAT);
        //国际化
        me.setI18nDefaultBaseName("i18n/messages");
        me.setI18nDefaultLocale("zh_CN");
        // 设置 Json 转换工厂实现类，更多说明见第 12 章
        JacksonFactory jacksonFactory = new JacksonFactory();
        ObjectMapper objectMapper = ((Jackson) jacksonFactory.getJson()).getObjectMapper();
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        SimpleModule module = new SimpleModule();
        module.addSerializer(Record.class, new JsonSerializer<Record>() {
            @Override
            public void serialize(Record record, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeObject(record.getColumns());
            }
        });
        module.addDeserializer(Record.class, new JsonDeserializer<Record>() {
            @Override
            public Record deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                JsonNode node = p.getCodec().readTree(p);
                return new Record().setColumns(objectMapper.convertValue(node, new TypeReference<Map<String, Object>>() {
                }));
            }
        });
        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                String str = p.getText();
                return DateUtil.parse(str);
            }
        });
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                if (String.class.isAssignableFrom(beanDesc.getBeanClass())) {
                    return new StdDeserializer<Object>(beanDesc.getBeanClass()) {
                        @Override
                        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                            String str = p.getText();
                            if (CommonAttribute.DATE_FORMAT.replaceAll("'", "").length() == str.length()) {
                                try {
                                    return TimeKit.parse(str, CommonAttribute.DATE_FORMAT);
                                } catch (Exception e) {
                                    try {
                                        return TimeKit.parse(str, CommonAttribute.DATE_FORMAT_T);
                                    } catch (Exception e1) {
                                        return str;
                                    }
                                }
                            }
                            return str;
                        }
                    };
                }
                return deserializer;
            }

            @Override
            public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
                // 排除特定字段
                builder.addIgnorable("@type");
                builder.addIgnorable("default");
                builder.addIgnorable("params");
                return builder;
            }
        });

        module.setSerializerModifier(new BeanSerializerModifier() {
            @Override
            public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
                List<BeanPropertyWriter> newWriters = new ArrayList<>();
                for (BeanPropertyWriter writer : beanProperties) {
//                    if (writer.getAnnotation(Sensitive.class) != null && writer.getType().isTypeOrSubTypeOf(String.class)) {
//                        // 如果带有 @Sensitive 注解，并且是字符串，则使用自定义处理
//                        JsonSerializer<Object> serializer = new SensitiveJsonSerializer(writer.getSerializer());
//                        writer.assignSerializer(serializer);
//                    }
                    if (SecurityUtils.desensitization() && writer.getType().isTypeOrSubTypeOf(String.class)) {
                        writer.assignSerializer(new StdSerializer<Object>(writer.getType()) {
                            @Override
                            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                                //固定key的脱敏
                                switch (writer.getName()) {
                                    case "phone":
                                    case "phonenumber":
                                        jsonGenerator.writeObject(DesensitizedUtil.desensitized((CharSequence) o, DesensitizedUtil.DesensitizedType.MOBILE_PHONE));
                                        break;
//                                    case "nickName":
//                                        jsonGenerator.writeObject(DesensitizedUtil.desensitized((CharSequence) o, DesensitizedUtil.DesensitizedType.CHINESE_NAME));
//                                        break;
                                    case "password":
                                        jsonGenerator.writeObject(DesensitizedUtil.desensitized((CharSequence) o, DesensitizedUtil.DesensitizedType.PASSWORD));
                                        break;
                                    case "email":
                                        jsonGenerator.writeObject(DesensitizedUtil.desensitized((CharSequence) o, DesensitizedUtil.DesensitizedType.EMAIL));
                                        break;
                                    default:
                                        jsonGenerator.writeObject(o);
                                }
                            }
                        });
                    }
                    newWriters.add(writer);
                }
                return newWriters;
            }
        });
        module.addSerializer(byte[].class, new JsonSerializer<byte[]>() {
            @Override
            public void serialize(byte[] bytes, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeObject(new String(bytes, CommonAttribute.ENCODING));
            }
        });
        module.addDeserializer(byte[].class, new JsonDeserializer<byte[]>() {
            @Override
            public byte[] deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                String str = p.getText();
                return str.getBytes(CommonAttribute.ENCODING);
            }
        });
        objectMapper.registerModule(module);
        me.setJsonFactory(jacksonFactory);
        // 配置上传文件最大数据量，默认 10M
        me.setMaxPostSize(10 * 1024 * 1024);
        // 配置为 slf4j 日志系统，否则默认将使用 log4j
        me.setToSlf4jLogFactory();
        // 开启解析 json 请求，5.0.0 版本新增功能 fastjson
        //me.setResolveJsonRequest(true);
    }

    /**
     * 配置路由
     */
    @Override
    public void configRoute(Routes me) {
        // me.add("/test", TestController.class);
        // 路由扫描，jfinal 4.9.03 新增功能。参数 "com.xxx." 表示扫描被限定的包名，
        // 扫描仅会在该包以及该包的子包下进行配合@Path
        me.scan("com.jianjun.");
    }

    @Override
    public void configEngine(Engine me) {
        // 配置极速模式，性能提升 13%
        Engine.setFastMode(true);
        // jfinal 4.9.02 新增配置：支持中文表达式、中文变量名、中文方法名、中文模板函数名
        Engine.setChineseExpression(true);
        // 支持模板热加载，绝大多数生产环境下也建议配置成 true，除非是极端高性能的场景
        me.setDevMode(true);
        // 添加共享模板函数
        //me.addSharedFunction("_layout.html");
        // 开启 HTML 压缩功能，分隔字符参数可配置为：'\n' 与 ' '
        me.setCompressorOn(' ');
    }

    /**
     * 配置插件
     */
    @Override
    public void configPlugin(Plugins me) {
        //数据库连接控件
        configDbForHikariCp(me);
        // 用于缓存模块的ehcache服务
        me.add(new EhCachePlugin(CacheManager.create(new Configuration()
                .diskStore(new DiskStoreConfiguration().path("java.io.tmpdir/tmp_ehcache"))
                .defaultCache(new CacheConfiguration()
                        .maxEntriesLocalHeap(1000)
                        .logging(true)
                )
        )));
        // 用于缓存模块的redis服务
        if (redisEnabled) {
            RedisPlugin mainRedis = new RedisPlugin("main", redisHost, redisPort, redisTimeout, redisPassword, redisDatabase);
            me.add(mainRedis);
        }
    }

    /**
     * 配置全局拦截器
     */
    @Override
    public void configInterceptor(Interceptors me) {
        //跨域
        me.add(new CORSGlobalInterceptor());
        //jwt认证
        me.add(new JwtAuthenticationTokenInterceptor());
        //权限拦截器
        me.add(new DataScopeInterceptor());
    }

    /**
     * 配置处理器
     */
    @Override
    public void configHandler(Handlers me) {
        // action 处理器修改后
        me.setActionHandler(new ActionHandler());
        if (xssEnabled) {
            // XSS过滤
            me.add(new XssHandler(xssExcludes));
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        ThreadUtil.execAsync(() -> {
            TimeInterval timer = DateUtil.timer();
            try {
                //sql预处理
                if (Db.use().getConfig().getDialect().isOracle()) {
                    Db.queryInt("SELECT 1 FROM dual");
                } else {
                    Db.queryInt("SELECT 1");
                }
                //json预处理
                UtilJackson.parse("[{}]", new TypeReference<List<Record>>() {
                });
                Okv.create().toJson();
                //时间格式化预处理
                TimeKit.format(new Date(), CommonAttribute.DATE_FORMAT);
                //日志打印空行生成新日志文件
                log.debug("");
                log.info("");
                log.warn("");
                log.error("");
                sysDictTypeService.init();
                sysJobService.init();
            } finally {
                log.info("{}ms", timer.interval());
            }
        });

    }

    @Override
    public void onStop() {
        super.onStop();
        CronUtil.stop();
    }

    /**
     * 配置数据库 HikariCp
     */
    private void configDbForHikariCp(Plugins me) {
        String dbConfig = AllConfig.DATABASE.get("database.register", "main");
        String[] dataBases = dbConfig.split(",");
        for (String dbKey : dataBases) {
            HikariCpPlugin hcp = new HikariCpPlugin(AllConfig.DATABASE.get(dbKey + ".jdbcUrl"), AllConfig.DATABASE.get(dbKey + ".user"), AllConfig.DATABASE.get(dbKey + ".password"), AllConfig.DATABASE.get(dbKey + ".driver"));
            //添加其他参数
            ActiveRecordPlugin arp;
            if (DbKit.MAIN_CONFIG_NAME.equals(dbKey)) {
                arp = new ActiveRecordPlugin(new IDataSourceProvider() {
                    @Override
                    public DataSource getDataSource() {
                        return new NullDataSource() {
                            @Override
                            public Connection getConnection() throws SQLException {
                                return hcp.getDataSource().getConnection();
                            }
                        };
                    }

                    @Override
                    public String getJdbcUrl() {
                        return hcp.getJdbcUrl();
                    }
                });
                //添加sql模板文件
                arp.addSqlTemplate("template/main.sql");
                // 所有映射在 MappingKit 中自动化搞定
                _MappingKit.mapping(arp);
            } else {
                arp = new ActiveRecordPlugin(dbKey, hcp);
            }
            arp.setAutoConfigDialect(true);
            arp.setContainerFactory(new CaseInsensitiveContainerFactory());
            arp.setShowSql(true);
            arp.getEngine().setCompressorOn(' ');
            me.add(hcp);
            me.add(arp);
        }
    }

    /**
     * 配置数据库 Druid
     */
    private void configDbForDruid(Plugins me) {
        loadPropertyFile(CommonAttribute.DATABASE_PROPERTIES);
        String dbConfig = AllConfig.DATABASE.get("database.register", "main");
        String[] dataBases = dbConfig.split(",");
        for (String dbKey : dataBases) {
            DruidPlugin dp = new DruidPlugin(AllConfig.DATABASE.get(dbKey + ".jdbcUrl"), AllConfig.DATABASE.get(dbKey + ".user"), AllConfig.DATABASE.get(dbKey + ".password"), AllConfig.DATABASE.get(dbKey + ".driver"));
            //添加其他参数
            ActiveRecordPlugin arp;
            if (DbKit.MAIN_CONFIG_NAME.equals(dbKey)) {
                arp = new ActiveRecordPlugin(new IDataSourceProvider() {
                    @Override
                    public DataSource getDataSource() {
                        return new NullDataSource() {
                            @Override
                            public Connection getConnection() throws SQLException {
                                return dp.getDataSource().getConnection();
                            }
                        };
                    }

                    @Override
                    public String getJdbcUrl() {
                        return dp.getJdbcUrl();
                    }
                });


                //添加sql模板文件
                arp.addSqlTemplate("template/main.sql");
                // 所有映射在 MappingKit 中自动化搞定
                _MappingKit.mapping(arp);
            } else {
                arp = new ActiveRecordPlugin(dbKey, dp);
            }
            arp.setAutoConfigDialect(true);
            arp.setContainerFactory(new CaseInsensitiveContainerFactory());
            arp.setShowSql(true);
            arp.getEngine().setCompressorOn(' ');
            me.add(dp);
            me.add(arp);
        }
    }
}
